#include "downloader.h"

#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>

#include "functions.h"

Downloader::Downloader(QObject *parent)
    :QObject(parent), downloaded(0)
{
}

Downloader::~Downloader()
{
    qDebug() << "Downloader Destructor called";
}

void Downloader::startDownload(const QString &url_str, const QDir &saveDir, QString filename, int nt)
{
    url = QUrl(url_str);
    this->saveDir = saveDir;
    this->nt = nt;

    auto filePath = saveDir.absoluteFilePath(filename);

    this->file = new QFile(filePath, this);
    file->open(QIODevice::WriteOnly);

    qDebug() << "path: " << url.path();

    QNetworkRequest request(url);

    // head request manager
    auto hnam = new QNetworkAccessManager(this);

    // connect signal before sending request
    connect(hnam, SIGNAL(finished(QNetworkReply*)), this, SLOT(headerReceived(QNetworkReply*)));

    // send head request
    hnam->head(request);

}

void Downloader::initRequests(int nt, qint64 contentLength)
{
    for(int i=0; i < nt; i++) {
        // all parts are equal except last
        qint64 partFileSize = contentLength/nt;

        qint64 first = i * partFileSize;
        qint64 last = (i+1) * partFileSize - 1;
        if(i == (nt-1)) {
            last = contentLength - 1;
        }

        QNetworkRequest request(url);

        QString headerValue = QString("bytes=") + QString::number(first) + "-" + QString::number(last);
        request.setRawHeader("Range",  headerValue.toStdString().c_str());

        auto nam = new QNetworkAccessManager(this);
        auto reply = nam->get(request);

        auto writer = new Writer(file, this);
        writer->setReply(reply);

        // connect to writer
        connect(reply, SIGNAL(readyRead()), writer, SLOT(bytesAvailable()));
        connect(writer, SIGNAL(newDataDownloaded(qint64)), this, SLOT(newDataDownloaded(qint64)));
    }
}

void Downloader::downloadUsingSingleConnection()
{
    QNetworkRequest request(url);

    auto nam = new QNetworkAccessManager(this);
    auto reply = nam->get(request);

    auto writer = new Writer(file, this);
    writer->setReply(reply);

    connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
    connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
    // connect to writer
    connect(reply, SIGNAL(readyRead()), writer, SLOT(bytesAvailable()));
    connect(writer, SIGNAL(newDataDownloaded(qint64)), this, SLOT(newDataDownloaded(qint64)));
}

void Downloader::onError(QNetworkReply::NetworkError error)
{
    qDebug() << error;
}

void Downloader::onFinished(QNetworkReply *reply)
{
    qDebug() << "finished";
    if(reply->hasRawHeader("Location"))
    {
        QString redirectedUrl = reply->header(QNetworkRequest::LocationHeader).toString();
        emit errorOccured("Redirected to " + redirectedUrl);
    }
}

void Downloader::headerReceived(QNetworkReply *reply)
{
    if(reply->error()) {
        emit errorOccured(reply->errorString());
        qDebug() << reply->errorString();
        reply->deleteLater();
        return;
    }
    if(reply->hasRawHeader("Accept-Ranges")) {
        auto range = reply->rawHeader("Accept-Ranges");
        qDebug() << "range:" << range;

        QVariant var = reply->header(QNetworkRequest::ContentLengthHeader);
        if(var.isValid()) {
            contentLength = var.toInt();
            qDebug() << "resume support available\n";
            qDebug() << "length: " << contentLength << "\n";
            initRequests(nt, contentLength);
        } else {
            emit errorOccured("Unable to determine content length\n"
                              "File will be downloaded using single thread.");
            downloadUsingSingleConnection();
        }
    } else {
        emit errorOccured("Resume/parallel download support is not available.\n"
                          "File will be downloaded using single thread.");
        qDebug() << "resume support unavailable";
        auto headers = reply->rawHeaderList();
        qDebug() << headers.length();
        qDebug() << headers;
        downloadUsingSingleConnection();
    }

    reply->deleteLater();
}

void Downloader::newDataDownloaded(qint64 n)
{
    downloaded += n;
    auto progress = downloaded * 100 / contentLength;
    emit progressChanged(progress);
    qDebug() << "Downloader:" << progress;
    if(progress == 100) {
        file->close();
        qDebug() << "file closed";
        emit finished();
    }
}
